#include "swoole.h"
#include "Server.h"
#include <sys/epoll.h>

typedef struct swReactorEpoll_s swReactorEpoll;

typedef struct _swFd
{
	uint32_t fd;
	uint32_t fdtype;
} swFd;

struct swReactorEpoll_s{
	int epfd;
	struct epoll_event *events;
};

static int swReactorEpoll_add(swReactor *reactor, int fd, int fdtype);
static int swReactorEpoll_set(swReactor *reactor, int fd, int fdtype);
static int swReactorEpoll_del(swReactor *reactor, int fd);
static int swReactorEpoll_wait(swReactor *reactor, struct timeval *timeo);
static void swReactorEpoll_free(swReactor *reactor);

static sw_inline int swReactorEpoll_event_set(int fdtype)
{
    uint32_t flag = 0;
    if (swReactor_event_read(fdtype))
    {
        flag |= EPOLLIN;
    }
    if (swReactor_event_write(fdtype))
    {
        flag |= EPOLLOUT;
    }
    if (fdtype & SW_EVENT_ONCE)
    {
        flag |= EPOLLONESHOT;
    }
    if (swReactor_event_error(fdtype))
    {
        //flag |= (EPOLLRDHUP);
        flag |= (EPOLLRDHUP | EPOLLHUP | EPOLLERR);
    }
    return flag;
}

int swReactorEpoll_create(swReactor *reactor, int max_event_num){
	swReactorEpoll *reactor_object = malloc(sizeof(swReactorEpoll));
	if(reactor_object == NULL){
		return -1;
	}

	bzero(reactor_object,sizeof(swReactorEpoll));
	reactor->object = reactor_object;
	reactor->max_event_num = max_event_num;

	reactor_object->events = calloc(max_event_num,sizeof(struct epoll_event)); 
	if(reactor_object->events == NULL){
		free(reactor_object);
		return -1;
	}

	reactor_object->epfd = epoll_create(512);
	if(reactor_object->epfd < 0){
		free(reactor_object);
		return -1;
	}

	reactor->add = swReactorEpoll_add;
	reactor->set = swReactorEpoll_set;
	reactor->del = swReactorEpoll_del;
	reactor->wait = swReactorEpoll_wait;
	reactor->free = swReactorEpoll_free;

	return 0;
}

static int swReactorEpoll_add(swReactor *reactor, int fd, int fdtype){
	swReactorEpoll *object = reactor->object;
	struct epoll_event e;
	swFd fd_;

	bzero(&e,sizeof(struct epoll_event));

	e.events = swReactorEpoll_event_set(fdtype);
	fd_.fd = fd;
	fd_.fdtype = swReactor_fdtype(fdtype);
	memcpy(&(e.data.u64), &fd_, sizeof(fd_));

	if(epoll_ctl(object->epfd,EPOLL_CTL_ADD,fd,&e) < 0){
		return -1;
	}

	reactor->event_num++;

	return 0;
}

static int swReactorEpoll_set(swReactor *reactor, int fd, int fdtype){
	swReactorEpoll *object = reactor->object;
    swFd fd_;
    struct epoll_event e;
    int ret;

    bzero(&e, sizeof(struct epoll_event));
    e.events = swReactorEpoll_event_set(fdtype);

    if (e.events & EPOLLOUT)
    {
        assert(fd > 2);
    }

    fd_.fd = fd;
    fd_.fdtype = swReactor_fdtype(fdtype);
    memcpy(&(e.data.u64), &fd_, sizeof(fd_));

    ret = epoll_ctl(object->epfd, EPOLL_CTL_MOD, fd, &e);
    if (ret < 0)
    {
        return -1;
    }
    //execute parent method
    //swReactor_set(reactor, fd, fdtype);
    return 0;
}

static int swReactorEpoll_del(swReactor *reactor, int fd){
	swReactorEpoll *object = reactor->object;
    if (epoll_ctl(object->epfd, EPOLL_CTL_DEL, fd, NULL) < 0)
    {
        return -1;
    }

    reactor->event_num = reactor->event_num <= 0 ? 0 : reactor->event_num - 1;
	//the difference between socket_list and socket_array
    swReactor_del(reactor, fd);

    return 0;
}

static int swReactorEpoll_wait(swReactor *reactor, struct timeval *timeo){
	swReactorEpoll *object = reactor->object;
	int epoll_fd = object->epfd;
	int max_event_num = reactor->max_event_num;
	struct epoll_event *events = object->events;
	int msec,ret,n,i;
	swEvent event;
	swReactor_handle handle;
	int reactor_id = reactor->id;

	if(reactor->timeout_msec == 0){
		if(timeo == NULL){
			reactor->timeout_msec = -1;
		}else{
			reactor->timeout_msec = timeo->tv_sec * 1000 + timeo->tv_usec / 1000;
		}
	}

	reactor->start = 1;

	while(reactor->running > 0){
		//reactor->onBegin

		msec = reactor->timeout_msec;
		n = epoll_wait(epoll_fd,events,max_event_num,msec);

		if(n < 0){
			if(errno == EINTR){
				continue;
			}
			return -1;
		}else if(n == 0){
			//reactor->onTimeout
			continue;
		}

        for (i = 0; i < n; i++)
        {
            event.fd = events[i].data.u64;
            event.from_id = reactor_id;
            event.type = events[i].data.u64 >> 32;
            event.socket = swReactor_get(reactor, event.fd);

            //read
            if ((events[i].events & EPOLLIN) /*&& !event.socket->removed*/)
            {
                handle = swReactor_getHandle(reactor, SW_EVENT_READ, event.type);
                ret = handle(reactor, &event);
                if (ret < 0)
                {
                    printf("EPOLLIN handle failed. fd=%d.", event.fd);
                }
            }
            //write
            if ((events[i].events & EPOLLOUT) /*&& !event.socket->removed*/)
            {
                handle = swReactor_getHandle(reactor, SW_EVENT_WRITE, event.type);
                ret = handle(reactor, &event);
                if (ret < 0)
                {
                    printf("EPOLLOUT handle failed. fd=%d.", event.fd);
                }
            }
            //error
            if ((events[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) /*&& !event.socket->removed*/)
            {
                //ignore ERR and HUP, because event is already processed at IN and OUT handler.
                if ((events[i].events & EPOLLIN) || (events[i].events & EPOLLOUT))
                {
                    continue;
                }
                handle = swReactor_getHandle(reactor, SW_EVENT_ERROR, event.type);
                ret = handle(reactor, &event);
                if (ret < 0)
                {
                    printf("EPOLLERR handle failed. fd=%d.", event.fd);
                }
            }
			/*
            if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE))
            {
                reactor->event_num = reactor->event_num <= 0 ? 0 : reactor->event_num - 1;
                //swReactor_del(reactor, event.fd);
            }
			*/
        }
	}
}

static void swReactorEpoll_free(swReactor *reactor){
	swReactorEpoll *object = reactor->object;
	close(object->epfd);
	free(object->events);
	free(object);
}
